The setup - Montly SPY 16 delta strangles, 45DTE, hold to expiration, 1-5x loss of credit recieved
Stop losses are a popular strategy used by traders to achieve better performance by closing out of a losing trade.
tastytrade ran a study where they compared managing 16 delta strangles in SPY, 45 DTE at expiration and 1x-5x credit received losses. They found that on average, stop losses hurt performance over the long run, because most of the trades you stopped out of eventually had a better P/L at expiration.
Here I will recreate this study and extend to include more underlyings to practice using the purrr package. This is the first attempt at recreating a Market Measure study and will be the basis of the tastytrade package on github.
library(tastytrade)
library(dplyr)
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
library(ggplot2)
library(plotly)
##
## Attaching package: 'plotly'
## The following object is masked from 'package:ggplot2':
##
## last_plot
## The following object is masked from 'package:stats':
##
## filter
## The following object is masked from 'package:graphics':
##
## layout
stock_list <- c("SPY", "IWM", "GLD", "QQQ", "DIA", "NFLX", "TLT", "XLE", "EEM",
"MA", "FB", "FXI", "SLV", "EWZ", "FXE", "TBT", "IBM")
#stock_list <- c("SPY", "IWM")
tar_dte <- 45
tar_delta_put <- -.16
tar_delta_call <- .16
all_loss_table <- data.frame()
study <- function(stock) {
options <- readRDS(paste0(here::here(), "/data/options/", stock, ".RDS")) %>%
dplyr::mutate(mid = (bid + ask) / 2)
monthly <- readRDS(paste0(here::here(), "/data/monthly.RDS"))
options_filtered <- options %>%
dplyr::filter(quotedate %in% monthly$date) %>%
dplyr::mutate(m_dte = abs(dte - tar_dte))
short_put_opens <- tastytrade::open_short_put(options_filtered, stock, tar_delta_put)
short_call_opens <- tastytrade::open_short_call(options_filtered, stock, tar_delta_call)
all_trades <- dplyr::full_join(short_call_opens, short_put_opens, by = c("quotedate", "expiration", "dte")) %>%
dplyr::mutate(credit = mid_put + mid_call)
all_closes <- data.frame()
possible_closes <- function(date, exp, c_strike, p_strike, credit) {
closes <- options %>%
dplyr::filter(quotedate > date,
quotedate <= exp,
expiration == exp) %>%
dplyr::filter((strike == c_strike & type == "call") |
(strike == p_strike & type == "put")) %>%
dplyr::group_by(quotedate) %>%
dplyr::mutate(open_date = as.Date(date, origin = "1970-01-01"),
open_credit = credit,
debit = sum(mid),
profit = open_credit - debit,
loss_1_x = ifelse(debit >= 2 * credit, 1, 0),
loss_2_x = ifelse(debit >= 3 * credit, 1, 0),
loss_3_x = ifelse(debit >= 4 * credit, 1, 0),
loss_4_x = ifelse(debit >= 5 * credit, 1, 0),
loss_5_x = ifelse(debit >= 6 * credit, 1, 0)) %>%
dplyr::ungroup() %>%
dplyr::select(symbol, quotedate, expiration, open_date, open_credit,
debit, profit, loss_1_x, loss_2_x, loss_3_x, loss_4_x, loss_5_x) %>%
dplyr::distinct()
all_closes <<- rbind(all_closes, closes)
}
invisible(purrr::pmap(list(all_trades$quotedate, all_trades$expiration, all_trades$strike_call,
all_trades$strike_put, all_trades$credit), possible_closes))
invisible(purrr::pmap(list(df = list(all_closes),
col_name = list("loss_1_x", "loss_2_x", "loss_3_x", "loss_4_x", "loss_5_x")),
tastytrade::stop_loss))
expiration <- all_closes %>%
dplyr::group_by(open_date) %>%
dplyr::filter(quotedate == expiration) %>%
dplyr::ungroup() %>%
dplyr::arrange(quotedate) %>%
dplyr::mutate(portfolio = cumsum(profit) * 100,
loss_type = "expiration")
this_loss_table <- dplyr::bind_rows(loss_1_x, loss_2_x) %>%
dplyr::bind_rows(loss_3_x) %>%
dplyr::bind_rows(loss_4_x) %>%
dplyr::bind_rows(loss_5_x) %>%
dplyr::bind_rows(expiration) %>%
dplyr::group_by(loss_type) %>%
dplyr::filter(open_date == max(open_date)) %>%
dplyr::ungroup() %>%
dplyr::mutate(rank = rank(-portfolio)) %>%
dplyr::select(symbol, loss_type, rank) %>%
tidyr::spread(., key = loss_type, value = rank)
all_loss_table <- rbind(all_loss_table, this_loss_table)
assign("all_loss_table", all_loss_table, envir = .GlobalEnv)
ggplot(data = loss_1_x, aes(x = quotedate, y = portfolio)) +
geom_line(aes(color = "1X Stop")) +
geom_line(data = loss_2_x, aes(color = "2X Stop")) +
geom_line(data = loss_3_x, aes(color = "3X Stop")) +
geom_line(data = loss_4_x, aes(color = "4X Stop")) +
geom_line(data = loss_5_x, aes(color = "5X Stop")) +
geom_line(data = expiration, aes(color = "expiration")) +
scale_fill_brewer() +
theme_dark() +
labs(title = stock, x = "Trade Open Date", y = "Portfolio Value")
}
purrr::map(stock_list, study)
## [[1]]
##
## [[2]]
##
## [[3]]
##
## [[4]]
##
## [[5]]
##
## [[6]]
##
## [[7]]
##
## [[8]]
##
## [[9]]
##
## [[10]]
##
## [[11]]
##
## [[12]]
##
## [[13]]
##
## [[14]]
##
## [[15]]
##
## [[16]]
##
## [[17]]
heat_map_data <- all_loss_table %>%
tibble::remove_rownames(.) %>%
tibble::column_to_rownames(var = "symbol")
## Warning: Setting row names on a tibble is deprecated.
heat_map_data <- as.matrix(heat_map_data)
plot_ly(x = colnames(heat_map_data), y = rownames(heat_map_data), z = heat_map_data, type = "heatmap", colors = colorRamp(c("red", "yellow")))